package common.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class CacheUtil {
    
    private final StringRedisTemplate stringRedisTemplate;
    
    public CacheUtil(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    
    /**
     * 获取缓存数据
     * @param key 缓存key
     * @param type 返回类型
     * @param dbFallback 数据库查询方法
     * @param lockKey 锁的key
     * @param ttl 缓存过期时间
     * @param timeUnit 时间单位
     * @return 缓存数据
     */
    public <T> T getWithCache(
            String key,
            Class<T> type,
            CacheDbFallback<T> dbFallback,
            String lockKey,
            long ttl,
            TimeUnit timeUnit) {
        // 1. 从redis查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        
        // 2. 判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3. 存在，则反序列化返回
            return JSONUtil.toBean(json, type);
        }
        
        // 4. 判断是否为空值
        if (json != null) {
            // 5. 为空值，则返回null
            return null;
        }
        
        // 6. 获取互斥锁
        T result = null;
        try {
            boolean isLock = tryLock(lockKey);
            if (!isLock) {
                // 7. 获取锁失败，休眠100ms后重试
                Thread.sleep(100);
                return getWithCache(key, type, dbFallback, lockKey, ttl, timeUnit);
            }
            
            // 8. 再次查询缓存
            json = stringRedisTemplate.opsForValue().get(key);
            if (StrUtil.isNotBlank(json)) {
                return JSONUtil.toBean(json, type);
            }
            
            // 9. 成功获取锁，查询数据库
            result = dbFallback.query();
            
            // 10. 判断数据是否存在
            if (result == null) {
                // 11. 数据不存在，建立空缓存防止缓存穿透
                stringRedisTemplate.opsForValue().set(key, "", ttl, timeUnit);
                return null;
            }
            
            // 12. 将数据写入redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(result), ttl, timeUnit);
            
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 13. 释放锁
            unlock(lockKey);
        }
        
        return result;
    }
    
    /**
     * 尝试获取锁
     * @param key 锁的key
     * @return 是否获取成功
     */
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(flag);
    }
    
    /**
     * 释放锁
     * @param key 锁的key
     */
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
    
    /**
     * 数据库查询接口
     * @param <T> 返回类型
     */
    @FunctionalInterface
    public interface CacheDbFallback<T> {
        T query();
    }
}